﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System.Windows.Input;
using System.ComponentModel;
namespace LockCrypt {
    // thanks Jani Giannoudis -  http://www.codeproject.com/KB/grid/ListView_layout_manager.aspx
    public class ListViewLayoutManager {

        public static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached(
            "Enabled",
            typeof(bool),
            typeof(ListViewLayoutManager),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnLayoutManagerEnabledChanged)));

        public ListViewLayoutManager(ListView listView) {
            if(listView == null) {
                throw new ArgumentNullException("listView");
            }
            this.listView = listView;
            this.listView.Loaded += new RoutedEventHandler(ListViewLoaded);
            this.listView.Unloaded += new RoutedEventHandler(ListViewUnloaded);
        }

        public ListView ListView {
            get { return this.listView; }
        }

        public ScrollBarVisibility VerticalScrollBarVisibility {
            get { return this.verticalScrollBarVisibility; }
            set { this.verticalScrollBarVisibility = value; }
        }

        public static void SetEnabled(DependencyObject dependencyObject, bool enabled) {
            dependencyObject.SetValue(EnabledProperty, enabled);
        }

        private void RegisterEvents(DependencyObject start) {
            for(int i = 0; i < VisualTreeHelper.GetChildrenCount(start); i++) {
                Visual childVisual = VisualTreeHelper.GetChild(start, i) as Visual;
                if(childVisual is Thumb) {
                    GridViewColumn gridViewColumn = FindParentColumn(childVisual);
                    if(gridViewColumn == null) {
                        continue;
                    }
                    Thumb thumb = childVisual as Thumb;
                    thumb.PreviewMouseMove += new MouseEventHandler(ThumbPreviewMouseMove);
                    thumb.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(ThumbPreviewMouseLeftButtonDown);
                    DependencyPropertyDescriptor.FromProperty(
                        GridViewColumn.WidthProperty,
                        typeof(GridViewColumn)).AddValueChanged(gridViewColumn, GridColumnWidthChanged);
                } else if(childVisual is GridViewColumnHeader) {
                    GridViewColumnHeader columnHeader = childVisual as GridViewColumnHeader;
                    columnHeader.SizeChanged += new SizeChangedEventHandler(GridColumnHeaderSizeChanged);
                } else if(this.scrollViewer == null && childVisual is ScrollViewer) {
                    this.scrollViewer = childVisual as ScrollViewer;
                    this.scrollViewer.ScrollChanged += new ScrollChangedEventHandler(ScrollViewerScrollChanged);
                    this.scrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;
                    this.scrollViewer.VerticalScrollBarVisibility = this.verticalScrollBarVisibility;
                }
                RegisterEvents(childVisual);
            }
        }

        private void UnregisterEvents(DependencyObject start) {
            for(int i = 0; i < VisualTreeHelper.GetChildrenCount(start); i++) {
                Visual childVisual = VisualTreeHelper.GetChild(start, i) as Visual;
                if(childVisual is Thumb) {
                    GridViewColumn gridViewColumn = FindParentColumn(childVisual);
                    if(gridViewColumn == null) {
                        continue;
                    }
                    Thumb thumb = childVisual as Thumb;
                    thumb.PreviewMouseMove -= new MouseEventHandler(ThumbPreviewMouseMove);
                    thumb.PreviewMouseLeftButtonDown -= new MouseButtonEventHandler(ThumbPreviewMouseLeftButtonDown);
                    DependencyPropertyDescriptor.FromProperty(
                        GridViewColumn.WidthProperty,
                        typeof(GridViewColumn)).RemoveValueChanged(gridViewColumn, GridColumnWidthChanged);
                } else if(childVisual is GridViewColumnHeader) {
                    GridViewColumnHeader columnHeader = childVisual as GridViewColumnHeader;
                    columnHeader.SizeChanged -= new SizeChangedEventHandler(GridColumnHeaderSizeChanged);
                } else if(this.scrollViewer == null && childVisual is ScrollViewer) {
                    this.scrollViewer = childVisual as ScrollViewer;
                    this.scrollViewer.ScrollChanged -= new ScrollChangedEventHandler(ScrollViewerScrollChanged);
                }
                UnregisterEvents(childVisual);
            }
        }

        private GridViewColumn FindParentColumn(DependencyObject element) {
            if(element == null) {
                return null;
            }
            while(element != null) {
                if(element is GridViewColumnHeader) {
                    return ((GridViewColumnHeader)element).Column;
                }
                element = VisualTreeHelper.GetParent(element);
            }
            return null;
        }

        private GridViewColumnHeader FindColumnHeader(DependencyObject start, GridViewColumn gridViewColumn) {
            for(int i = 0; i < VisualTreeHelper.GetChildrenCount(start); i++) {
                Visual childVisual = VisualTreeHelper.GetChild(start, i) as Visual;
                if(childVisual is GridViewColumnHeader) {
                    GridViewColumnHeader gridViewHeader = childVisual as GridViewColumnHeader;
                    if(gridViewHeader != null && gridViewHeader.Column == gridViewColumn) {
                        return gridViewHeader;
                    }
                }
                GridViewColumnHeader childGridViewHeader = FindColumnHeader(childVisual, gridViewColumn);
                if(childGridViewHeader != null) {
                    return childGridViewHeader;
                }
            }
            return null;
        }

        private void InitColumns() {
            GridView view = this.listView.View as GridView;
            if(view == null) {
                return;
            }
            foreach(GridViewColumn gridViewColumn in view.Columns) {
                if(!RangeColumn.IsRangeColumn(gridViewColumn)) {
                    continue;
                }
                double? minWidth = RangeColumn.GetRangeMinWidth(gridViewColumn);
                double? maxWidth = RangeColumn.GetRangeMaxWidth(gridViewColumn);
                if(!minWidth.HasValue && !maxWidth.HasValue) {
                    continue;
                }
                GridViewColumnHeader columnHeader = FindColumnHeader(this.listView, gridViewColumn);
                if(columnHeader == null) {
                    continue;
                }
                double actualWidth = columnHeader.ActualWidth;
                if(minWidth.HasValue) {
                    columnHeader.MinWidth = minWidth.Value;
                    if(!double.IsInfinity(actualWidth) && actualWidth < columnHeader.MinWidth) {
                        gridViewColumn.Width = columnHeader.MinWidth;
                    }
                }
                if(maxWidth.HasValue) {
                    columnHeader.MaxWidth = maxWidth.Value;
                    if(!double.IsInfinity(actualWidth) && actualWidth > columnHeader.MaxWidth) {
                        gridViewColumn.Width = columnHeader.MaxWidth;
                    }
                }
            }
        }

        protected virtual void ResizeColumns() {
            GridView view = this.listView.View as GridView;
            if(view == null || view.Columns.Count == 0) {
                return;
            }

            double actualWidth = double.PositiveInfinity;
            if(this.scrollViewer != null) {
                actualWidth = this.scrollViewer.ViewportWidth;
            }
            if(double.IsInfinity(actualWidth)) {
                actualWidth = this.listView.ActualWidth;
            }
            if(double.IsInfinity(actualWidth) || actualWidth <= 0) {
                return;
            }
            double resizeableRegionCount = 0;
            double otherColumnsWidth = 0;
            foreach(GridViewColumn gridViewColumn in view.Columns) {
                if(ProportionalColumn.IsProportionalColumn(gridViewColumn)) {
                    resizeableRegionCount += ProportionalColumn.GetProportionalWidth(gridViewColumn).Value;
                } else {
                    otherColumnsWidth += gridViewColumn.ActualWidth;
                }
            }
            if(resizeableRegionCount <= 0) {
                this.scrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;

                GridViewColumn fillColumn = null;
                for(int i = 0; i < view.Columns.Count; i++) {
                    GridViewColumn gridViewColumn = view.Columns[i];
                    if(IsFillColumn(gridViewColumn)) {
                        fillColumn = gridViewColumn;
                        break;
                    }
                }
                if(fillColumn != null) {
                    double otherColumnsWithoutFillWidth = otherColumnsWidth - fillColumn.ActualWidth;
                    double fillWidth = actualWidth - otherColumnsWithoutFillWidth;
                    if(fillWidth > 0) {
                        double? minWidth = RangeColumn.GetRangeMinWidth(fillColumn);
                        double? maxWidth = RangeColumn.GetRangeMaxWidth(fillColumn);
                        bool setWidth = true;
                        if(minWidth.HasValue && fillWidth < minWidth.Value) {
                            setWidth = false;
                        }
                        if(maxWidth.HasValue && fillWidth > maxWidth.Value) {
                            setWidth = false;
                        }
                        if(setWidth) {
                            this.scrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;
                            fillColumn.Width = fillWidth;
                        }
                    }
                }
                return;
            }
            double resizeableColumnsWidth = actualWidth - otherColumnsWidth;
            if(resizeableColumnsWidth <= 0) {
                return;
            }

            double resizeableRegionWidth = resizeableColumnsWidth / resizeableRegionCount;
            foreach(GridViewColumn gridViewColumn in view.Columns) {
                if(ProportionalColumn.IsProportionalColumn(gridViewColumn)) {
                    gridViewColumn.Width = ProportionalColumn.GetProportionalWidth(gridViewColumn).Value * resizeableRegionWidth;
                }
            }
        }

        private double SetRangeColumnToBounds(GridViewColumn gridViewColumn) {
            double startWidth = gridViewColumn.Width;
            double? minWidth = RangeColumn.GetRangeMinWidth(gridViewColumn);
            double? maxWidth = RangeColumn.GetRangeMaxWidth(gridViewColumn);
            if((minWidth.HasValue && maxWidth.HasValue) && (minWidth > maxWidth)) {
                return 0;
            }
            if(minWidth.HasValue && gridViewColumn.Width < minWidth.Value) {
                gridViewColumn.Width = minWidth.Value;
            } else if(maxWidth.HasValue && gridViewColumn.Width > maxWidth.Value) {
                gridViewColumn.Width = maxWidth.Value;
            }
            return gridViewColumn.Width - startWidth;
        }

        private bool IsFillColumn(GridViewColumn gridViewColumn) {
            if(gridViewColumn == null) {
                return false;
            }
            GridView view = this.listView.View as GridView;
            if(view == null || view.Columns.Count == 0) {
                return false;
            }
            bool? isFillCoumn = RangeColumn.GetRangeIsFillColumn(gridViewColumn);
            return isFillCoumn.HasValue && isFillCoumn.Value == true;
        }

        private void DoResizeColumns() {
            if(this.resizing) {
                return;
            }
            this.resizing = true;
            try {
                ResizeColumns();
            } catch {
                throw;
            } finally {
                this.resizing = false;
            }
        }

        private void ListViewLoaded(object sender, RoutedEventArgs e) {
            RegisterEvents(this.listView);
            InitColumns();
            DoResizeColumns();
            this.loaded = true;
        }

        private void ListViewUnloaded(object sender, RoutedEventArgs e) {
            if(!this.loaded) {
                return;
            }
            UnregisterEvents(this.listView);
            this.loaded = false;
        }

        private void ThumbPreviewMouseMove(object sender, MouseEventArgs e) {
            Thumb thumb = sender as Thumb;
            GridViewColumn gridViewColumn = FindParentColumn(thumb);
            if(gridViewColumn == null) {
                return;
            }

            if(ProportionalColumn.IsProportionalColumn(gridViewColumn) ||
                IsFillColumn(gridViewColumn)) {
                thumb.Cursor = null;
                return;
            }

            if(thumb.IsMouseCaptured && RangeColumn.IsRangeColumn(gridViewColumn)) {
                double? minWidth = RangeColumn.GetRangeMinWidth(gridViewColumn);
                double? maxWidth = RangeColumn.GetRangeMaxWidth(gridViewColumn);
                if((minWidth.HasValue && maxWidth.HasValue) && (minWidth > maxWidth)) {
                    return;
                }
                if(this.resizeCursor == null) {
                    this.resizeCursor = thumb.Cursor;
                }
                if(minWidth.HasValue && gridViewColumn.Width <= minWidth.Value) {
                    thumb.Cursor = Cursors.No;
                } else if(maxWidth.HasValue && gridViewColumn.Width >= maxWidth.Value) {
                    thumb.Cursor = Cursors.No;
                } else {
                    thumb.Cursor = this.resizeCursor;
                }
            }
        }

        private void ThumbPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
            Thumb thumb = sender as Thumb;
            GridViewColumn gridViewColumn = FindParentColumn(thumb);

            if(ProportionalColumn.IsProportionalColumn(gridViewColumn) ||
                IsFillColumn(gridViewColumn)) {
                e.Handled = true;
                return;
            }
        }

        private void GridColumnWidthChanged(object sender, EventArgs e) {
            if(!this.loaded) {
                return;
            }
            GridViewColumn gridViewColumn = sender as GridViewColumn;

            if(ProportionalColumn.IsProportionalColumn(gridViewColumn)) {
                return;
            }

            if(RangeColumn.IsRangeColumn(gridViewColumn)) {
                if(gridViewColumn.Width.Equals(double.NaN)) {
                    this.autoSizedColumn = gridViewColumn;
                    return;
                }

                if(SetRangeColumnToBounds(gridViewColumn) != 0) {
                    return;
                }
            }
            DoResizeColumns();
        }

        private void GridColumnHeaderSizeChanged(object sender, SizeChangedEventArgs e) {
            if(this.autoSizedColumn == null) {
                return;
            }
            GridViewColumnHeader gridViewColumnHeader = sender as GridViewColumnHeader;
            if(gridViewColumnHeader.Column == this.autoSizedColumn) {
                if(gridViewColumnHeader.Width.Equals(double.NaN)) {
                    gridViewColumnHeader.Column.Width = gridViewColumnHeader.ActualWidth;
                    DoResizeColumns();
                }
                this.autoSizedColumn = null;
            }
        }

        private void ScrollViewerScrollChanged(object sender, ScrollChangedEventArgs e) {
            if(this.loaded && e.ViewportWidthChange != 0) {
                DoResizeColumns();
            }
        }

        private static void OnLayoutManagerEnabledChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) {
            ListView listView = dependencyObject as ListView;
            if(listView != null) {
                bool enabled = (bool)e.NewValue;
                if(enabled) {
                    new ListViewLayoutManager(listView);
                }
            }
        }

        private readonly ListView listView;
        private ScrollViewer scrollViewer;
        private bool loaded = false;
        private bool resizing = false;
        private Cursor resizeCursor;
        private ScrollBarVisibility verticalScrollBarVisibility = ScrollBarVisibility.Auto;
        private GridViewColumn autoSizedColumn;
    }
    public abstract class LayoutColumn {
        protected static bool HasPropertyValue(GridViewColumn column, DependencyProperty dp) {
            if(column == null) {
                throw new ArgumentNullException("column");
            }
            object value = column.ReadLocalValue(dp);
            if(value != null && value.GetType() == dp.PropertyType) {
                return true;
            }
            return false;
        }

        protected static double? GetColumnWidth(GridViewColumn column, DependencyProperty dp) {
            if(column == null) {
                throw new ArgumentNullException("column");
            }
            object value = column.ReadLocalValue(dp);
            if(value != null && value.GetType() == dp.PropertyType) {
                return (double)value;
            }
            return null;
        }
    }
    public sealed class RangeColumn : LayoutColumn {
        public static readonly DependencyProperty MinWidthProperty =
            DependencyProperty.RegisterAttached(
                "MinWidth",
                typeof(double),
                typeof(RangeColumn));

        public static readonly DependencyProperty MaxWidthProperty =
            DependencyProperty.RegisterAttached(
                "MaxWidth",
                typeof(double),
                typeof(RangeColumn));

        public static readonly DependencyProperty IsFillColumnProperty =
            DependencyProperty.RegisterAttached(
                "IsFillColumn",
                typeof(bool),
                typeof(RangeColumn));

        private RangeColumn() {
        }

        public static double GetMinWidth(DependencyObject obj) {
            return (double)obj.GetValue(MinWidthProperty);
        }

        public static void SetMinWidth(DependencyObject obj, double minWidth) {
            obj.SetValue(MinWidthProperty, minWidth);
        }

        public static double GetMaxWidth(DependencyObject obj) {
            return (double)obj.GetValue(MaxWidthProperty);
        }

        public static void SetMaxWidth(DependencyObject obj, double maxWidth) {
            obj.SetValue(MaxWidthProperty, maxWidth);
        }

        public static bool GetIsFillColumn(DependencyObject obj) {
            return (bool)obj.GetValue(IsFillColumnProperty);
        }

        public static void SetIsFillColumn(DependencyObject obj, bool isFillColumn) {
            obj.SetValue(IsFillColumnProperty, isFillColumn);
        }

        public static bool IsRangeColumn(GridViewColumn column) {
            if(column == null) {
                return false;
            }
            return
                HasPropertyValue(column, MinWidthProperty) ||
                HasPropertyValue(column, MaxWidthProperty) ||
                HasPropertyValue(column, IsFillColumnProperty);
        }

        public static double? GetRangeMinWidth(GridViewColumn column) {
            return GetColumnWidth(column, MinWidthProperty);
        }

        public static double? GetRangeMaxWidth(GridViewColumn column) {
            return GetColumnWidth(column, MaxWidthProperty);
        }

        public static bool? GetRangeIsFillColumn(GridViewColumn column) {
            if(column == null) {
                throw new ArgumentNullException("column");
            }
            object value = column.ReadLocalValue(IsFillColumnProperty);
            if(value != null && value.GetType() == IsFillColumnProperty.PropertyType) {
                return (bool)value;
            }
            return null;
        }

        public static GridViewColumn ApplyWidth(GridViewColumn gridViewColumn, double minWidth,
            double width, double maxWidth) {
            return ApplyWidth(gridViewColumn, minWidth, width, maxWidth, false);
        }

        public static GridViewColumn ApplyWidth(GridViewColumn gridViewColumn, double minWidth,
            double width, double maxWidth, bool isFillColumn) {
            SetMinWidth(gridViewColumn, minWidth);
            gridViewColumn.Width = width;
            SetMaxWidth(gridViewColumn, maxWidth);
            SetIsFillColumn(gridViewColumn, isFillColumn);
            return gridViewColumn;
        }
    }
    public sealed class ProportionalColumn : LayoutColumn {

        public static readonly DependencyProperty WidthProperty =
            DependencyProperty.RegisterAttached(
                "Width",
                typeof(double),
                typeof(ProportionalColumn));

        private ProportionalColumn() {
        }

        public static double GetWidth(DependencyObject obj) {
            return (double)obj.GetValue(WidthProperty);
        }

        public static void SetWidth(DependencyObject obj, double width) {
            obj.SetValue(WidthProperty, width);
        }

        public static bool IsProportionalColumn(GridViewColumn column) {
            if(column == null) {
                return false;
            }
            return HasPropertyValue(column, WidthProperty);
        }
        public static double? GetProportionalWidth(GridViewColumn column) {
            return GetColumnWidth(column, WidthProperty);
        }
    }
    public sealed class FixedColumn : LayoutColumn {

        public static readonly DependencyProperty WidthProperty =
            DependencyProperty.RegisterAttached(
                "Width",
                typeof(double),
                typeof(FixedColumn));

        private FixedColumn() {
        }

        public static double GetWidth(DependencyObject obj) {
            return (double)obj.GetValue(WidthProperty);
        }

        public static void SetWidth(DependencyObject obj, double width) {
            obj.SetValue(WidthProperty, width);
        }

        public static bool IsFixedColumn(GridViewColumn column) {
            if(column == null) {
                return false;
            }
            return HasPropertyValue(column, FixedColumn.WidthProperty);
        }

        public static double? GetFixedWidth(GridViewColumn column) {
            return GetColumnWidth(column, FixedColumn.WidthProperty);
        }

        public static GridViewColumn ApplyWidth(GridViewColumn gridViewColumn, double width) {
            SetWidth(gridViewColumn, width);
            return gridViewColumn;
        }
    } 
}